Conversation
1. 리포트 페이지에서 생성 완료 후 다른 페이지로 이동했을 때 우측 하단 모달 띄우지 않도록 수정 2. 개요보다 분석이 먼저 끝나는 경우에도 step2를 채우도록 수정 - 시작 전: 1 - 둘 다 미완료: 2 - overview만 완료: 3 - analysis만 완료: 3 - 둘 다 완료: 4
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 리포트 생성 과정에서 사용자 경험을 크게 향상시키기 위해 실시간 진행률 표시 기능과 전역 알림 시스템을 도입합니다. 기존의 주기적인 서버 폴링 방식에서 벗어나 Server-Sent Events(SSE)를 활용하여 리포트 생성 상태를 즉각적으로 반영하며, 사용자에게 더욱 명확하고 부드러운 피드백을 제공합니다. 또한, 리포트 생성 중 발생하는 다양한 상황에 대한 시각적 및 기능적 처리를 개선하여 전반적인 애플리케이션의 반응성을 높였습니다. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
이 PR은 리포트 생성 요청 시 프로그레스바 기능을 추가하고, 상태 추적 방식을 기존의 폴링에서 서버-전송 이벤트(SSE)를 사용하는 방식으로 성공적으로 리팩터링했습니다. GlobalProcessingModal과 GlobalToast 컴포넌트의 도입으로 사용자 경험이 크게 개선되었습니다. 코드는 전반적으로 잘 구조화되어 있지만, 설정 관리, 에러 처리, 코드 복잡성 측면에서 몇 가지 개선할 점이 보입니다. 특히 API URL 하드코딩, 훅의 에러 처리 방식, 컴포넌트 내 복잡한 로직 등은 상세 리뷰 의견을 참고하여 개선하면 코드의 안정성과 유지보수성을 더욱 높일 수 있을 것입니다.
Note: Security Review did not run due to the size of the PR.
| const renderIcon = () => { | ||
| if (type === 'error') return <ErrorIcon /> | ||
| return <ErrorIcon /> | ||
| } |
| const tokenRaw = window.localStorage.getItem(LOCAL_STORAGE_KEY.accessToken) | ||
| const token = tokenRaw ? JSON.parse(tokenRaw) : null |
There was a problem hiding this comment.
localStorage에서 가져온 값을 JSON.parse로 파싱할 때, 값이 유효한 JSON이 아닐 경우 에러가 발생하여 훅의 동작이 멈출 수 있습니다. try-catch 블록으로 감싸서 예외를 안전하게 처리하는 것이 좋습니다. 또한, 토큰을 가져오는 로직이 여러 곳에서 반복되므로 유틸리티 함수로 분리하는 것을 고려해볼 수 있습니다.
const tokenRaw = window.localStorage.getItem(LOCAL_STORAGE_KEY.accessToken)
let token = null;
if (tokenRaw) {
try {
token = JSON.parse(tokenRaw);
} catch (error) {
console.error('Failed to parse auth token from localStorage', error);
}
}| useEffect(() => { | ||
| if (!isProcessing) { | ||
| setDisplayStep(null) | ||
| firstVisibleRef.current = false | ||
| return | ||
| } | ||
|
|
||
| if (!firstVisibleRef.current) { | ||
| firstVisibleRef.current = true | ||
| setDisplayStep(1) | ||
|
|
||
| const timer = setTimeout(() => { | ||
| setDisplayStep(currentStep) | ||
| }, 500) // 최소 노출 시간 | ||
|
|
||
| return () => clearTimeout(timer) | ||
| } | ||
|
|
||
| setDisplayStep(currentStep) | ||
| }, [isProcessing, currentStep]) | ||
|
|
||
| // 리포트 생성 상태 동기화 | ||
| useEffect(() => { | ||
| if (isProcessing && normalizedVideoData) { | ||
| addReport({ | ||
| reportId, | ||
| videoId, | ||
| title: normalizedVideoData.videoTitle, | ||
| }) | ||
| } | ||
| }, [isProcessing, reportId, videoId, normalizedVideoData, addReport]) | ||
|
|
||
| useEffect(() => { | ||
| if (!isProcessing && rawResult) { | ||
| const { overviewStatus, analysisStatus } = rawResult | ||
|
|
||
| if (overviewStatus === 'COMPLETED' && analysisStatus === 'COMPLETED') { | ||
| hasSeenCompletionRef.current = true | ||
| } | ||
| } | ||
| }, [isProcessing, rawResult]) | ||
|
|
||
| // 항상 최신 rawResult 저장 | ||
| useEffect(() => { | ||
| latestResultRef.current = rawResult | ||
| }, [rawResult]) | ||
|
|
||
| // 완료를 직접 본 경우 처리 | ||
| useEffect(() => { | ||
| if (!rawResult) return | ||
|
|
||
| const { overviewStatus, analysisStatus } = rawResult | ||
| const isCompleted = overviewStatus === 'COMPLETED' && analysisStatus === 'COMPLETED' | ||
|
|
||
| if (!isCompleted) return | ||
|
|
||
| // 이 페이지에서 완료를 직접 본 경우 | ||
| if (!isProcessing) { | ||
| hasSeenCompletionRef.current = true | ||
| removeReport(reportId) | ||
| } | ||
| }, [rawResult, isProcessing, reportId, removeReport]) | ||
|
|
||
| // 진짜 unmount 시에만 실행 | ||
| useEffect(() => { | ||
| return () => { | ||
| const result = latestResultRef.current | ||
| if (!result) return | ||
|
|
||
| const { overviewStatus, analysisStatus } = result | ||
|
|
||
| const isCompleted = overviewStatus === 'COMPLETED' && analysisStatus === 'COMPLETED' | ||
|
|
||
| // 내가 완료를 직접 보지 않았고, | ||
| // 완료된 상태라면 → background 완료 처리 | ||
| if (isCompleted && !hasSeenCompletionRef.current) { | ||
| addCompletedReport(reportId) | ||
| removeReport(reportId) | ||
| } | ||
| } | ||
| // 의존성 비움 (unmount 전용) | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []) |
| isCurrent && [ | ||
| step.id === 1 ? 'delay-0' : 'delay-500', | ||
| step.id === 4 ? 'w-full duration-300' : 'w-[85%] duration-[20000ms]', | ||
| ], | ||
| !isActive && 'w-0 duration-0 delay-0' |
💡 Related Issue
closes #260
✅ Summary
PR에 대한 간단한 요약을 작성합니다.
📝 Description
💬 리뷰 요구 사항